package edu.ncsu.csc.ip.routing.dv;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;

/**
 * Class <code>RouterLocal</code>
 * 
 * Simulation of Routers that directly interact with each other via method
 * calls.
 * 
 * @author Moin Ayazifar <mayazif@ncsu.edu>
 * @author Steven Elliott <sdelliot@ncsu.edu>
 * 
 */

public class RouterLocal {

	final public Node localNode;

	// Number of times the DV has been updated;
	private int iteration = 0;

	// Total number of DV updates sent to neighbors
	private int updatesSent = 0;

	// Total number of DV received from neighbors
	private int updatesReceived = 0;

	// Queue of Distance Vector changes submitted by neighbors
	final public BlockingQueue<Node> updatedNeighborDvQueue = new LinkedBlockingQueue<Node>();

	public RouterLocal(Node node) {
		node.router = this;
		this.localNode = node;
	}

	public void start() throws Exception {
		SenderThread sender = new SenderThread(this);
		new Thread(sender).start();
	}

	public void initialize() throws Exception {
		localNode.distanceVector.put(localNode,
				new RouteEntity(localNode, 0.0d));
		// Initialize local DV and prepare it to send to neighbors
		for (RouteEntity re : localNode.neighborsMap.values())
			localNode.distanceVector.put(re.nextNode, re);

		// initial increment
		incrementIteration();
		// send it to all other neighbors
		for (RouteEntity re : localNode.neighborsMap.values()) {
			re.nextNode.router.updatedNeighborDvQueue.add(localNode.clone());

			re.nextNode.router.incrementUpdatesReceived();
			incrementUpdatesSent();
		}
	}

	public synchronized Map<Node, RouteEntity> calculateNewDV(Node neighbor) {

		if (neighbor != null && localNode.neighborsMap.containsKey(neighbor)) {
			boolean changed = false;
			Map<Node, RouteEntity> newLocalDv = localNode.clone().distanceVector;

			// recalculate the distance to each distance node in the neighbor's
			// DV
			for (Node destNode : neighbor.distanceVector.keySet()) {
				double newWeightToDest = localNode.neighborsMap.get(neighbor).weight
						+ neighbor.distanceVector.get(destNode).weight;
				if (!newLocalDv.containsKey(destNode)
						|| newLocalDv.get(destNode).weight > newWeightToDest) {
					newLocalDv.put(destNode, new RouteEntity(neighbor,
							newWeightToDest));
					changed = true;
				}
			}
			if (changed)
				return newLocalDv;
		} else {
			if (neighbor == null)
				System.err.println("Neighbor is null");
			else if (!localNode.neighborsMap.containsKey(neighbor))
				System.err.println(" Neighbour: " + neighbor
						+ " is not in the neighbors map");
		}
		return null;

	}

	public List<Node> getLeastCostPathTo(Node target) {
		List<Node> path = new ArrayList<Node>();
		for (Node node = localNode; !node.equals(target); node = node.distanceVector
				.get(target).nextNode)
			path.add(node);
		path.add(target);
		return path;
	}

	public int getIteration() {
		return iteration;
	}

	public synchronized void incrementIteration() {
		iteration++;
	}

	public int getUpdatesSent() {
		return updatesSent;
	}

	public synchronized void incrementUpdatesSent() {
		updatesSent++;
	}

	public int getUpdateReceived() {
		return updatesReceived;
	}

	public synchronized void incrementUpdatesReceived() {
		updatesReceived++;
	}

}
